package com.starlinkdt.framework.aspectj;

import com.starlinkdt.common.annotation.OaiServAuth;
import com.starlinkdt.common.constant.HttpStatus;
import com.starlinkdt.common.domain.system.entity.OaiAcc;
import com.starlinkdt.common.domain.system.entity.OaiPerm;
import com.starlinkdt.common.domain.system.entity.OaiServ;
import com.starlinkdt.common.enums.BusinessStatus;
import com.starlinkdt.common.exception.ServiceException;
import com.starlinkdt.common.mapper.system.OaiAccMapper;
import com.starlinkdt.common.mapper.system.OaiReqLogMapper;
import com.starlinkdt.common.utils.ServletUtils;
import com.starlinkdt.common.utils.oaiauth.OaiServAuthPermUtils;
import com.starlinkdt.common.utils.oaiauth.OaiServAuthSignUtils;
import com.starlinkdt.common.domain.system.entity.OaiReqLog;
import com.starlinkdt.common.context.RequestIdContextHolder;
import com.starlinkdt.common.utils.ip.AddressUtils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 接口鉴权处理
 *
 * @author system
 */
@Aspect
@Component
public class OaiServAuthAspect
{
    @Autowired
    private OaiServAuthPermUtils oaiServAuthPermUtils;

    @Autowired
    private OaiAccMapper oaiAccMapper;

    @Autowired
    private OaiReqLogMapper oaiReqLogMapper;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String REDIS_KEY_PRE = "openapi:auth:aspectj:";

    /** 计算操作消耗时间 */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    public OaiServAuthAspect() {
    }

    @Before(value = "@annotation(oaiServAuth)")
    public void doBefore(JoinPoint joinPoint, OaiServAuth oaiServAuth) throws Throwable
    {
        // 设置开始请求时间
        TIME_THREADLOCAL.set(System.currentTimeMillis());

        // 应用签名认证
        verifyAuthorization();

        // 获取oaiServ
        OaiServ oaiServ = oaiServAuthPermUtils.getOaiServ(ServletUtils.getRequest().getRequestURI());

        if (oaiServAuth.hasPermission()) {
            // 获取oaiAcc
            OaiAcc oaiAcc = oaiServAuthPermUtils.getOaiAcc(ServletUtils.getRequest().getParameter("a"));

            // 获取oaiPerm
            OaiPerm oaiPerm = oaiServAuthPermUtils.getOaiPerm(oaiAcc.getAccBizNo(), oaiServ.getServBizNo());
        }
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(oaiServAuth)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, OaiServAuth oaiServAuth, Object jsonResult) {
        // 记录日志
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(oaiServAuth)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OaiServAuth oaiServAuth, Exception e) {
        // 记录日志
        handleLog(joinPoint, e, null);
    }

    private void verifyAuthorization () throws Exception
    {
        // 获取accAppId
        String accAppId = ServletUtils.getRequest().getParameter("a");
        // 获取URI
        String requestUri = ServletUtils.getRequest().getRequestURI();
        // 获取请求IP
        String requestIp = ServletUtils.getRequest().getRemoteAddr();
        // 获取Timestamp
        String timestamp = ServletUtils.getRequest().getParameter("t");
        // 获取签名认证
        String authorization = ServletUtils.getRequest().getHeader("Authorization");

        // 获取签名参数
        Map<String, String> params = new HashMap<>();
        params.put("u", requestUri);
        params.put("t", timestamp);
        params.put("a", accAppId);
        params.put("sign", authorization);
        params.forEach((k, v) -> {
            if (v == null || v.isEmpty()) {
                throw new ServiceException("签名参数缺失", HttpStatus.BAD_REQUEST);
            }
        });

        // 检查重复请求
        String vSignRedisKey = REDIS_KEY_PRE + "verify:" + authorization;
        String vSign = redisTemplate.opsForValue().get(vSignRedisKey);
        if (vSign == null || vSign.isEmpty()) {
            redisTemplate.opsForValue().set(vSignRedisKey, "1", 600, TimeUnit.SECONDS);
        } else {
            throw new ServiceException("签名重复请求", HttpStatus.UNAUTHORIZED);
        }

        // 获取AppKey
        String vKeyRedisKey = REDIS_KEY_PRE + "verify:" + accAppId;
        String vKey = redisTemplate.opsForValue().get(vKeyRedisKey);
        if (vKey == null || vKey.isEmpty()) {
            OaiAcc oaiAcc = oaiAccMapper.selectOaiAccByAccAppId(accAppId);
            if (oaiAcc == null) {
                vKey = accAppId;
            } else {
                vKey = oaiAcc.getAccAppKey();
            }
            redisTemplate.opsForValue().set(vKeyRedisKey, vKey, 600, TimeUnit.SECONDS);
        }
        if (vKey.equals(accAppId)) {
            throw new ServiceException("AppID不存在", HttpStatus.UNAUTHORIZED);
        }

        // 签名校验
        if (! OaiServAuthSignUtils.isSignValid(params, vKey, OaiServAuthSignUtils.SignType.MD5)) {
            throw new ServiceException("签名校验失败", HttpStatus.UNAUTHORIZED);
        }

    }

    private void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
    {
        // 获取accAppId
        String accAppId = ServletUtils.getRequest().getParameter("a");
        // 获取请求IP
        String requestIp = ServletUtils.getRequest().getRemoteAddr();
        // 获取Timestamp
        String timestamp = ServletUtils.getRequest().getParameter("t");
        // 获取签名认证
        String authorization = ServletUtils.getRequest().getHeader("Authorization");

        // 请求STATUS
        int status = BusinessStatus.SUCCESS.ordinal();
        String errorMsg = "";
        if (e != null) {
            status = BusinessStatus.FAIL.ordinal();
            errorMsg = e.getMessage();
        }

        OaiReqLog oaiReqLog = new OaiReqLog();
        oaiReqLog.setReqId(RequestIdContextHolder.getContext());
        oaiReqLog.setAppId(accAppId);
        oaiReqLog.setReqUri(ServletUtils.getRequest().getRequestURI());
        oaiReqLog.setReqMethod(ServletUtils.getRequest().getMethod());
        oaiReqLog.setReqIp(requestIp);
        oaiReqLog.setReqAddress(AddressUtils.getRealAddressByIP(requestIp));
        oaiReqLog.setReqStatus(status);
        oaiReqLog.setErrorMsg(errorMsg);
        oaiReqLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
        oaiReqLog.setReqTimestamp(Long.parseLong(timestamp));
        oaiReqLog.setAuth(authorization);
        // 写入请求记录
        oaiReqLogMapper.insert(oaiReqLog);

        // 清除开始请求时间
        TIME_THREADLOCAL.remove();
    }
}
